package download

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
)

// HTTP 头字段常量
const (
	HeaderContentLength       = "Content-Length"
	HeaderLastModified        = "Last-Modified"
	SmallFileThreshold  int64 = 32 * 1024 * 1024 // 32 MB
	ChunkSize           int64 = 32 * 1024 * 1024 // 32 MB
	MaxConcurrentChunks       = 16               // 最大并发块数
)

var bufferPool = sync.Pool{
	New: func() interface{} {
		return make([]byte, ChunkSize)
	},
}

// 直接下载，用于下载小文件（不使用断点续传）
func directDownloadFile(fileUrl, filePath string, timeout time.Duration) (int64, error) {
	if filePath == "" {
		return 0, fmt.Errorf("文件路径不能为空")
	}

	client := &http.Client{}
	if timeout > 0 {
		client.Timeout = timeout
	}

	resp, err := client.Get(fileUrl)
	if err != nil {
		return 0, fmt.Errorf("下载文件失败: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return 0, fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode)
	}

	file, err := os.Create(filePath)
	if err != nil {
		return 0, fmt.Errorf("创建文件失败: %w", err)
	}
	defer file.Close()

	written, err := io.Copy(file, resp.Body)
	if err != nil {
		return 0, fmt.Errorf("写入文件失败: %w", err)
	}

	return written, nil
}

// 下载文件，支持断点续传
func resumeDownloadFile(urlPath, localFilePath string, fileSize int64, lastModified time.Time) (int64, error) {
	if localFilePath == "" {
		return 0, fmt.Errorf("文件路径不能为空")
	}

	metaFilePath := localFilePath + ".meta"
	tmpFilePath := localFilePath + ".tmp"

	if fileExists(localFilePath) {
		if err := validateLocalFile(localFilePath, fileSize, lastModified); err == nil {
			return 0, nil
		}
		_ = os.Remove(localFilePath)
	}

	if !fileExists(tmpFilePath) || !fileExists(metaFilePath) {
		_ = os.Remove(tmpFilePath)
		_ = os.Remove(metaFilePath)

		if err := preallocateFile(tmpFilePath, fileSize); err != nil {
			return 0, fmt.Errorf("无法预分配临时文件: %w", err)
		}
	}
	var downloadErr error
	if fileSize <= SmallFileThreshold {
		downloadErr = downloadNotUseChunk(urlPath, tmpFilePath, metaFilePath, fileSize)
	} else {
		downloadErr = downloadUseChunk(urlPath, tmpFilePath, metaFilePath, fileSize)
	}
	if downloadErr != nil {
		return 0, downloadErr
	}
	// 将临时文件重命名为目标文件
	if err := os.Rename(tmpFilePath, localFilePath); err != nil {
		return 0, fmt.Errorf("无法重命名临时文件: %w", err)
	}

	// 更新文件的最后修改时间

	if err := os.Chtimes(localFilePath, lastModified, lastModified); err != nil {
		return 0, fmt.Errorf("无法更新文件修改时间: %w", err)
	}

	_ = os.Remove(metaFilePath) // 删除元文件

	return fileSize, nil
}

// 下载文件，使用分块
func downloadUseChunk(fileUrl, tmpFilePath, metaFilePath string, fileSize int64) error {
	var wg sync.WaitGroup
	mu := &sync.Mutex{} // 初始化 mu 为一个新的 Mutex 实例
	var downloadErr error

	completed := make([]bool, (fileSize+ChunkSize-1)/ChunkSize)
	readMetaFile(metaFilePath, completed)

	chunkSemaphore := make(chan struct{}, MaxConcurrentChunks)

	file, err := os.OpenFile(tmpFilePath, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("无法打开临时文件 (路径: %s): %w", tmpFilePath, err)
	}
	defer file.Close()

	for i := int64(0); i < fileSize; i += ChunkSize {
		if completed[i/ChunkSize] {
			continue
		}

		wg.Add(1)
		chunkSemaphore <- struct{}{}

		go func(offset int64) {
			defer wg.Done()
			defer func() { <-chunkSemaphore }()

			if err := downloadChunk(fileUrl, file, offset, fileSize, completed, mu, metaFilePath); err != nil {
				mu.Lock()
				downloadErr = err
				mu.Unlock()
			}
		}(i)
	}

	wg.Wait()
	return downloadErr
}

// 下载文件块
func downloadChunk(fileUrl string, file *os.File, offset, fileSize int64, completed []bool, mu *sync.Mutex, metaFilePath string) error {
	client := &http.Client{}
	req, err := http.NewRequest("GET", fileUrl, nil)
	if err != nil {
		return fmt.Errorf("创建 HTTP 请求失败: %w", err)
	}

	end := offset + ChunkSize - 1
	if end >= fileSize {
		end = fileSize - 1
	}
	req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, end))

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("下载文件块失败 (%d-%d): %w", offset, end, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusPartialContent {
		return fmt.Errorf("服务器未返回 Partial Content 状态码: %d", resp.StatusCode)
	}

	buffer := bufferPool.Get().([]byte)
	defer bufferPool.Put(buffer)

	n, err := io.ReadFull(resp.Body, buffer[:end-offset+1])
	if err != nil && err != io.ErrUnexpectedEOF {
		return fmt.Errorf("读取响应体失败: %w", err)
	}

	mu.Lock()
	defer mu.Unlock()

	if _, err := file.WriteAt(buffer[:n], offset); err != nil {
		return fmt.Errorf("写入临时文件失败 (偏移: %d): %w", offset, err)
	}

	// 更新分块完成状态
	completed[offset/ChunkSize] = true

	// 更新元文件
	if err := updateMetaFileForChunk(metaFilePath, completed); err != nil {
		return fmt.Errorf("更新元文件失败: %w", err)
	}

	return nil
}

// 下载文件，不使用分块
func downloadNotUseChunk(fileUrl, tmpFilePath, metaFilePath string, fileSize int64) error {
	file, err := os.OpenFile(tmpFilePath, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("无法打开临时文件 (路径: %s): %w", tmpFilePath, err)
	}
	defer file.Close()

	if err := lockFile(file); err != nil {
		return fmt.Errorf("无法加锁临时文件 (路径: %s): %w", tmpFilePath, err)
	}
	defer unlockFile(file)

	currentOffset, err := getCurrentOffset(metaFilePath)
	if err != nil {
		return err
	}

	client := &http.Client{}
	req, err := http.NewRequest("GET", fileUrl, nil)
	if err != nil {
		return fmt.Errorf("创建 HTTP 请求失败: %w", err)
	}
	req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", currentOffset, fileSize-1))

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("下载文件失败: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusPartialContent {
		return fmt.Errorf("服务器未返回 Partial Content 状态码: %d", resp.StatusCode)
	}

	if _, err := file.Seek(currentOffset, io.SeekStart); err != nil {
		return fmt.Errorf("无法设置文件写入位置 (偏移: %d): %w", currentOffset, err)
	}

	buffer := bufferPool.Get().([]byte)
	defer bufferPool.Put(buffer)

	for {
		n, err := resp.Body.Read(buffer)
		if n > 0 {
			if _, err := file.Write(buffer[:n]); err != nil {
				return fmt.Errorf("写入临时文件失败 (路径: %s): %w", tmpFilePath, err)
			}

			// 更新元文件
			currentOffset += int64(n)
			if err := updateMetaFileForSequential(metaFilePath, currentOffset); err != nil {
				return fmt.Errorf("更新元文件失败: %w", err)
			}
		}

		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("读取响应体失败: %w", err)
		}
	}

	return nil
}

// 获取当前偏移
func getCurrentOffset(metaFilePath string) (int64, error) {
	if !fileExists(metaFilePath) {
		return 0, nil
	}

	data, err := os.ReadFile(metaFilePath)
	if err != nil {
		return 0, fmt.Errorf("无法读取元文件 (路径: %s): %w", metaFilePath, err)
	}

	currentOffset, err := strconv.ParseInt(string(data), 10, 64)
	if err != nil {
		return 0, fmt.Errorf("解析元文件内容失败: %w", err)
	}

	return currentOffset, nil
}

// 更新元文件，仅用于分块下载
func updateMetaFileForChunk(metaFilePath string, completed []bool) error {
	var data string
	for i, done := range completed {
		if done {
			data += fmt.Sprintf("%d\n", i)
		}
	}
	return os.WriteFile(metaFilePath, []byte(data), 0644)
}

// 更新元文件，仅用于顺序下载
func updateMetaFileForSequential(metaFilePath string, currentOffset int64) error {
	return os.WriteFile(metaFilePath, []byte(fmt.Sprintf("%d", currentOffset)), 0644)
}

// 判断本地文件和元数据是否匹配
func validateLocalFile(filePath string, fileSize int64, lastModified time.Time) error {
	info, err := os.Stat(filePath)
	if err != nil {
		return err
	}

	if info.Size() == fileSize && info.ModTime().Equal(lastModified) {
		return nil
	}

	return fmt.Errorf("本地文件不匹配")
}

// 读取元文件
func readMetaFile(metaFilePath string, completed []bool) {
	if !fileExists(metaFilePath) {
		return
	}

	data, err := os.ReadFile(metaFilePath)
	if err != nil {
		return
	}

	lines := string(data)
	for _, line := range lines {
		index, err := strconv.Atoi(string(line))
		if err == nil && index < len(completed) {
			completed[index] = true
		}
	}
}

// 判断文件是否存在
func fileExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return err == nil
}

// 预分配文件大小
func preallocateFile(filePath string, size int64) error {
	file, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

	return file.Truncate(size)
}

// getRemoteFileMetadata 获取远程文件的元数据（文件大小和最后修改时间）
func getRemoteFileMetadata(urlPath string) (int64, time.Time, error) {
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	req, err := http.NewRequest("HEAD", urlPath, nil)
	if err != nil {
		return 0, time.Time{}, fmt.Errorf("创建 HTTP 请求失败: %w", err)
	}

	resp, err := client.Do(req)
	if err != nil {
		return 0, time.Time{}, fmt.Errorf("请求远程文件元数据失败: %w", err)
	}
	defer resp.Body.Close()

	// 获取文件大小
	contentLength := resp.Header.Get(HeaderContentLength)
	fileSize, err := strconv.ParseInt(contentLength, 10, 64)
	if err != nil {
		return 0, time.Time{}, fmt.Errorf("解析Content-Length失败: %w", err)
	}

	// 获取最后修改时间
	lastModifiedStr := resp.Header.Get(HeaderLastModified)
	lastModified, err := time.Parse(time.RFC1123, lastModifiedStr)
	if err != nil {
		return 0, time.Time{}, fmt.Errorf("解析Last-Modified失败: %w", err)
	}

	return fileSize, lastModified, nil
}
